﻿namespace SugarTank.Mobile.Managers
{
    using System;
    using System.ComponentModel;
    using System.Device.Location;
    using System.Threading;
    using Contracts;
    using Helpers;

    public class EmulatedGeographicManager : IGeographicManager
    {
        /// <summary>
        /// Backgorund worker to provide emulation.
        /// </summary>
        private BackgroundWorker worker;

        #region IGeographicManager Members

        /// <summary>
        /// Occurs when [geo watcher status changed].
        /// </summary>
        public event EventHandler<EventArgs<GeoPositionStatus>> GeoWatcherStatusChanged;

        /// <summary>
        /// Occurs when [geo watcher failed to start].
        /// </summary>
        public event EventHandler<EventArgs> GeoWatcherFailedToStart;

        /// <summary>
        /// Occurs when [geo watcher started].
        /// </summary>
        public event EventHandler<EventArgs> GeoWatcherStarted;

        /// <summary>
        /// Occurs when [geo watcher stopped].
        /// </summary>
        public event EventHandler<EventArgs> GeoWatcherStopped;

        /// <summary>
        /// Occurs when [geo watcher position changed].
        /// </summary>
        public event EventHandler<EventArgs<GeoPositionChangedEventArgs<GeoCoordinate>>> GeoWatcherPositionChanged;

        /// <summary>
        /// Starts the geo watcher.
        /// </summary>
        /// <param name="movementThreshold">The movement threshold.</param>
        /// <param name="accuracy">The accuracy.</param>
        public void StartGeoWatcher(double movementThreshold, GeoPositionAccuracy accuracy)
        {
            this.worker = new BackgroundWorker();
            this.worker.WorkerSupportsCancellation = true;
            this.worker.DoWork += this.DoWork;
            this.worker.RunWorkerCompleted += this.RunWorkerCompleted;
            this.worker.RunWorkerAsync();

            this.GeoWatcherStarted.FireEvent(this, () => EventArgs.Empty);

            this.OnWatcherStatusChanged(this, new GeoPositionStatusChangedEventArgs(GeoPositionStatus.NoData));
            this.OnWatcherStatusChanged(this, new GeoPositionStatusChangedEventArgs(GeoPositionStatus.Initializing));
            this.OnWatcherStatusChanged(this, new GeoPositionStatusChangedEventArgs(GeoPositionStatus.Ready));
        }

        /// <summary>
        /// Stops the geo watcher.
        /// </summary>
        public void StopGeoWatcher()
        {
            this.worker.CancelAsync();
        }

        #endregion

        /// <summary>
        /// Runs the worker completed.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.ComponentModel.RunWorkerCompletedEventArgs"/> instance containing the event data.</param>
        private void RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            this.GeoWatcherStopped.FireEvent(this, () => EventArgs.Empty);
        }

        /// <summary>
        /// Does the work.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.ComponentModel.DoWorkEventArgs"/> instance containing the event data.</param>
        private void DoWork(object sender, DoWorkEventArgs e)
        {
            var random = new Random();

            while (!this.worker.CancellationPending)
            {
                Thread.Sleep(random.Next(1000, 2000));

                double latitude = (random.NextDouble()*180.0) - 90.0;
                double longitude = (random.NextDouble()*360.0) - 180.0;

                this.OnWatcherPositionChanged(this,
                                              new GeoPositionChangedEventArgs<GeoCoordinate>(
                                                  new GeoPosition<GeoCoordinate>(DateTimeOffset.Now,
                                                                                 new GeoCoordinate(latitude, longitude))));
            }
        }

        /// <summary>
        /// Called when [watcher position changed].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The instance containing the event data.</param>
        private void OnWatcherPositionChanged(object sender, GeoPositionChangedEventArgs<GeoCoordinate> e)
        {
            this.GeoWatcherPositionChanged.FireEvent(sender, () => new EventArgs<GeoPositionChangedEventArgs<GeoCoordinate>>(e));
        }

        /// <summary>
        /// Called when [watcher status changed].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Device.Location.GeoPositionStatusChangedEventArgs"/> instance containing the event data.</param>
        private void OnWatcherStatusChanged(object sender, GeoPositionStatusChangedEventArgs e)
        {
            this.GeoWatcherStatusChanged.FireEvent(sender, () => new EventArgs<GeoPositionStatus>(e.Status));
        }
    }
}